﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml.Linq;
using www.w3.org.Item2005.Atom;
using System.Text.RegularExpressions;
using System.IO.IsolatedStorage;
using System.Reflection;


namespace www.w3.org.Item2005.Atom
{
    /// <summary>
    /// This class gives you access to your Google Contacts. Create an instance, call getAuthCode() and then either getContactsStream() or getContactsFeed() depending on what you need. If you want to use the LINQ features, use getContactsFeed().
    /// </summary>
    public partial class feed
    {
        const string ERROR_STRING = "Error=";
        const string CT_STRING = "CaptchaToken=";
        const string CU_STRING = "CaptchaUrl=";
        const string AUTH_STRING = "Auth=";
        const string TEMP_FILE = "feed.xml";

        

        /// <summary>
        /// Gets the Authcode for a user
        /// </summary>
        /// <param name="username">The username for whom to get the authcode</param>
        /// <param name="passwd">The password to the users account</param>
        /// <param name="status">The status of the creationprocess</param>
        /// <param name="captchaInfo">The captchaInfo must be null if this is a request without captcha challenge! For a captcha request it should hold the captchInfo from the last request</param>
        /// <param name="captchaAnswer">The answer provided from the user.</param>
        /// <returns>The authcode if successful. Otherwise null</returns>
        public static string getAuthCode(string username, string passwd, out AuthCodeCreateStatus status, ref CaptchaInfo captchaInfo, string captchaAnswer)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(passwd))
                throw new ArgumentNullException("username and password must not be null");
            
            WebClient client = new WebClient();

            client.QueryString.Add("Email", username);
            client.QueryString.Add("Passwd", passwd);
            client.QueryString.Add("service", "cp");
            if (captchaInfo != null)
            {
                client.QueryString.Add("logintoken", captchaInfo.CaptchaToken);
                client.QueryString.Add("logincaptcha", captchaAnswer);
            }
            else
            {
                captchaInfo = new CaptchaInfo();
            }
            string response=null;
            try
            {
                response=client.DownloadString("https://www.google.com/accounts/ClientLogin");
            }
            catch (System.Net.WebException ex)
            {
                if (ex.Response == null)
                    throw;
                if (((HttpWebResponse)ex.Response).StatusCode==HttpStatusCode.Forbidden)
                {
                    StreamReader reader = new StreamReader(ex.Response.GetResponseStream());
                    
                    string str = reader.ReadLine();

                    

                    while (str != null)
                    {
                        if (str.Contains(CT_STRING))
                        {
                            str = str.Remove(0, CT_STRING.Length);
                            
                            captchaInfo.CaptchaToken = str;
                        }
                        if (str.Contains(CU_STRING))
                        {
                            str = str.Remove(0, CU_STRING.Length);
                            captchaInfo.CaptchaImage = new System.Drawing.Bitmap(client.OpenRead("https://www.google.com/accounts/" + str));
                            
                        }
                        if(str.Contains(ERROR_STRING))
                        {
                            str=str.Remove(0, ERROR_STRING.Length);
                            switch (str)
                            {
                                case "BadAuthentication":
                                    status = AuthCodeCreateStatus.BadAuthentication;
                                    captchaInfo = null;
                                    return null;
                                case "NotVerified":
                                    status = AuthCodeCreateStatus.NotVerified;
                                    captchaInfo = null;
                                    return null;
                                case "AccountDeleted":
                                    status = AuthCodeCreateStatus.AccountDeleted;
                                    captchaInfo = null;
                                    return null;
                                case "AccountDisabled":
                                    status = AuthCodeCreateStatus.AccountDisabled;
                                    captchaInfo = null;
                                    return null;
                                case "ServiceDisabled":
                                    status = AuthCodeCreateStatus.ServiceDisabled;
                                    captchaInfo = null;
                                    return null;
                                case "ServiceUnavailable":
                                    status = AuthCodeCreateStatus.ServiceUnavailable;
                                    captchaInfo = null;
                                    return null;
                                case "TermsNotAgreed":
                                    status = AuthCodeCreateStatus.TermsNotAgreed;
                                    captchaInfo = null;
                                    return null;
                                case "CaptchaRequired":
                                    status = AuthCodeCreateStatus.CaptchaRequired;
                                    return null;
                                default:
                                    status = AuthCodeCreateStatus.Unknown;
                                    break;
                            }
                            
                        }
                        str = reader.ReadLine();
                    }
                    status = AuthCodeCreateStatus.Unknown;
                    captchaInfo = null;
                    return null;
                }
            }
       

            status = AuthCodeCreateStatus.Success;
            captchaInfo = null;
            return Regex.Match(response, AUTH_STRING + ".*$").Value.Remove(0, AUTH_STRING.Length);
        }


        /// <summary>
        /// Gets the contacts for the provided user
        /// </summary>
        /// <param name="username">The user for whom to get the contacts</param>
        /// <param name="authCode">The authCode for this session</param>
        /// <param name="maxResults">The maximum number of contacts to be returned</param>
        /// <param name="showdeleted">The switch to decide if the returned feed holds information about delted contacts. Google only holds deleted contact information for 30 days.</param>
        /// <returns>The Stream to the contacts XML returned by Google</returns>
        public static Stream getContactsStream(string username, string authCode, int maxResults, bool showdeleted)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(authCode))
                throw new ArgumentNullException("username and authCode must not be null");

            WebClient client = new WebClient();
            client.Headers.Add("Authorization", "GoogleLogin auth="+authCode);

            if(showdeleted)
                client.QueryString.Add("showdeleted", "true");
            //client.QueryString.Add("alt", "rss");
            
            client.QueryString.Add("max-results", maxResults.ToString());
            

            try
            {
                return client.OpenRead("https://www.google.com/m8/feeds/contacts/" + username + "/base");
                
            }
            catch (WebException ex)
            {

                throw;
            }

        }

        /// <summary>
        /// This method returns the Contacts for the provided user in a LINQ enabled class
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="authCode">The authcode - you have to get the authcode first by using getAuthCode()</param>
        /// <param name="maxResults">The maximum number of results that should be returned</param>
        /// <param name="showdeleted">The switch to decide if the returned feed holds information about delted contacts. Google only holds deleted contact information for 30 days.</param>
        /// <returns>The LINQable Feed</returns>
        public static feed getContactsFeed(string username, string authCode, int maxResults, bool showdeleted)
        {

            Stream stream = getContactsStream(username, authCode, maxResults, showdeleted);

            String filepath;


            IsolatedStorageFile isoStore =
              IsolatedStorageFile.GetStore(IsolatedStorageScope.User
              | IsolatedStorageScope.Assembly, null, null);

            IsolatedStorageFileStream oStream =
              new IsolatedStorageFileStream(TEMP_FILE,
              FileMode.Create, isoStore);

            // Get the physical path using reflection
            filepath = oStream.GetType().GetField("m_FullPath",
                BindingFlags.Instance | BindingFlags.NonPublic).GetValue(oStream).ToString();

            StreamWriter streamWriter = new StreamWriter(oStream);

            StreamReader streamReader = new StreamReader(stream);

            streamWriter.Write(streamReader.ReadToEnd());
            streamWriter.Flush();
            streamWriter.Close();
            streamReader.Close();

            return feed.Load(filepath);
            

            
        }


        public HttpStatusCode createContact(string username, string authCode, www.w3.org.Item2005.Atom.feed.entryLocalType entry)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(authCode))
                throw new ArgumentNullException("username and authCode must not be null");


            WebRequest req = HttpWebRequest.Create("https://www.google.com/m8/feeds/contacts/" + username + "/base");
            WebResponse res=null;
            req.Method = "POST";
            req.ContentType = "application/atom+xml";
            req.Headers.Add("Authorization", "GoogleLogin auth=" + authCode);

            StreamWriter sr = new StreamWriter(req.GetRequestStream());
            sr.Write(entry.ToString());
            sr.Flush();

            //close doesn't seem to close the underlying stream (although it should) so we have to close it manually in finally block
            sr.Close();

            try
            {
                res = req.GetResponse();
            }
            catch (System.Net.WebException ex)
            {
                return ((HttpWebResponse)ex.Response).StatusCode;
            }
            finally
            {
                if (req != null) req.GetRequestStream().Close();
                if (res != null) res.GetResponseStream().Close();
            }
            

            return ((HttpWebResponse)res).StatusCode;

        }

        public HttpStatusCode deleteContact(string username, string authCode, www.w3.org.Item2005.Atom.feed.entryLocalType entry)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(authCode) || entry==null)
                throw new ArgumentNullException("username, authCode and entry must not be null or empty");


            WebRequest req = HttpWebRequest.Create(entry.link.Where(a => a.rel == "edit").Single().href);
            
            WebResponse res = null;
            req.Method = "POST";
            req.ContentType = "application/atom+xml";
            req.ContentLength = 0;
            req.Headers.Add("Authorization", "GoogleLogin auth=" + authCode);
            req.Headers.Add("X-HTTP-Method-Override", "DELETE");

            try
            {
                res = req.GetResponse();
            }
            catch (System.Net.WebException ex)
            {
                return ((HttpWebResponse)ex.Response).StatusCode;
            }

            return ((HttpWebResponse)res).StatusCode;

        }

        public HttpStatusCode updateContact(string username, string authCode, www.w3.org.Item2005.Atom.feed.entryLocalType entry)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(authCode) || entry==null)
                throw new ArgumentNullException("username, authCode and entry must not be null or empty");


            WebRequest req = HttpWebRequest.Create(entry.link.Where(a => a.rel == "edit").Single().href);

            WebResponse res = null;
            req.Method = "POST";
            req.ContentType = "application/atom+xml";
            req.Headers.Add("Authorization", "GoogleLogin auth=" + authCode);
            req.Headers.Add("X-HTTP-Method-Override", "PUT");

            StreamWriter sr = null;
            try
            {
                sr = new StreamWriter(req.GetRequestStream());
            }
            catch (WebException ex)
            {

                return ((HttpWebResponse)ex.Response).StatusCode;
            }
            
            sr.Write(entry.ToString());
            sr.Flush();

            

            //close doesn't seem to close the underlying stream (although it should) so we have to close it manually in finally block
            sr.Close();

            try
            {
                res = req.GetResponse();
            }
            catch (System.Net.WebException ex)
            {
                return ((HttpWebResponse)ex.Response).StatusCode;
            }
            finally
            {
                if (req != null) req.GetRequestStream().Close();
                if (res != null) res.GetResponseStream().Close();
            }


            return ((HttpWebResponse)res).StatusCode;

        }
        /*
        #region
        private Dictionary<SubmitStatus, entryLocalType> submissionDict = new Dictionary<SubmitStatus, entryLocalType>();

        public void SubmitChanges(string username, string authCode)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(authCode))
                throw new ArgumentNullException("username and authCode must not be null or empty");


            foreach (KeyValuePair<SubmitStatus,entryLocalType> item in submissionDict)
            {
                switch (item.Key)
                {

                    case SubmitStatus.insert:
                        createContact(username, authCode, item.Value);
                        break;
                    case SubmitStatus.delete:
                        deleteContact(username, authCode, item.Value);
                        break;
                    case SubmitStatus.update:
                        updateContact(username, authCode, item.Value);
                        break;
                    default:
                        break;
                }
            }
            submissionDict.Clear();
        }

        public void InsertOnSubmit(entryLocalType entry)
        {
            submissionDict.Add(SubmitStatus.insert, entry);
        }

        public void DeleteOnSubmit(entryLocalType entry)
        {
            submissionDict.Add(SubmitStatus.delete, entry);
        }

        public void UpdateOnSubmit(entryLocalType entry)
        {
            submissionDict.Add(SubmitStatus.update, entry);
        }

        #endregion
*/
    }

    enum SubmitStatus
    {
        insert,
        delete,
        update
    }
}
